package localSearch;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.HashSet;

import random.TestClusterBuilder;
import instrumentation.CoverageBranchTarget;
import instrumentation.TrackerCoverage;
import GA.*;
import util.Filter;
import util.GAConfig;
import Memetic.ReflectionExecutor;
import Operations.CreateObject;
import Operations.Invoke;
import Operations.Operation;
import Operations.ValuesGenerator;
import ReadWriteFieldsRelations.MethodRelations;

public class HillClimbing extends LocalSearchPopulation{
	private  TrackerCoverage tracker;
	private  long startTime;
	private  long elapsed;
	private  long TestTimeout;
	@Override	
	public Chromosome execute(){
		startTime=gaInfo.getStartTime();
		TestTimeout=gaInfo.getTestTimeOut();
		elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
		
		tracker = TrackerCoverage.getTracker();
		int trgt=getTargetToCover(tracker);
	//	trgt=39;
		if( trgt!= -1 ){
			 if (!GAConfig.Silent) 
				if(trgt==31)//||trgt==49||trgt==57) //return null ;
					System.out.println("Target to be cobvered: "+trgt );			

			 if (!GAConfig.Silent) 
				System.out.println("Target to be cobvered: "+trgt );
			Chromosome chrom =tracker.branchNotCoveredByIndividual.get(trgt);
//			System.out.println("Original chrom: "+chrom.toString() );

//			tracker.branchNotCoveredByIndividual.remove(trgt);
			Chromosome shortChrom = evalParts(chrom,trgt);
			if(shortChrom==null)
				shortChrom=chrom.clone();
			else
				tracker.branchNotCoveredByIndividual.put(trgt, shortChrom);
			//update the previous chromosome with the new one
//		System.out.println("shortChrom : "+shortChrom.toString() );

			Chromosome result = hillClimb(shortChrom,trgt);
			if(result!=null)
				return result;
		}

		return null;
	}
	public Chromosome evalParts(Chromosome chrom,int trgt){
		return ReflectionExecutor.reverseExecut(chrom,trgt);
		
	}
	public int getTargetToCover(TrackerCoverage tracker){
		int trgtBR=-1,BR;
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		List<Integer> keys = new ArrayList<Integer>(trackers.keySet());
		do {
			CoverageBranchTarget tempCondTarget=null;
			for ( BR = 0; BR < trackers.size(); BR++) {
				
				 tempCondTarget = (CoverageBranchTarget) trackers
						.get(keys.get(BR));
				if(tempCondTarget==null) continue;

				if(tempCondTarget.getfName_1().equals("$assertionsDisabled") || tempCondTarget.getfName_2().equals("$assertionsDisabled"))continue; 
				if(tempCondTarget.getType().contains("[") )
					continue;
				int attempt=tempCondTarget.getAttempt();

				if( tempCondTarget.getQuality() == Float.NEGATIVE_INFINITY) continue;
				if(tempCondTarget.getQuality() ==Float.POSITIVE_INFINITY && trgtBR==-1 &&(attempt!=gaInfo.getAttempt()))
						trgtBR=keys.get(BR);
				
				if (tempCondTarget.getQuality() !=Float.POSITIVE_INFINITY &&(attempt!=gaInfo.getAttempt())){
						trgtBR= keys.get(BR);
						if(tempCondTarget.getfName_1()=="" && tempCondTarget.getfName_2()=="")continue;
						break;
				}
//				System.out.println("HillClimbing.getTargerToCover: "+ trgtBR);
			}


			if(trgtBR==-1)
				gaInfo.setAttempt(gaInfo.getAttempt()+1);
			else{
				tempCondTarget = (CoverageBranchTarget) trackers.get(trgtBR);
				tempCondTarget.setAttempt(gaInfo.getAttempt());
			}

		}
		while(trgtBR==-1 && BR!=trackers.size());

		return trgtBR;
	
	}
	private static final int ITERATIONS = 1000;
	/** Number of attempts when a method is selected */
	private final int TTL_FIRST = 10;
	/** Number of attempts for operations able to reduce the branch distance */
	private final int TTL_IMPROVEMENT  = 50;
	public Chromosome hillClimb (Chromosome individual,int BR){
		int ttl = 0;//how many times that a position/ method can be targeted again
		Chromosome originalIndividual=individual.clone();
		Chromosome newIndividual=null;
		ReflectionExecutor.execute(individual);
	//	float oldFitness = tracker.calculateFitness(individual); 
		
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
	
//		System.out.println("hillClimb" + condTarget.toString());
	
		if(condTarget==null) return null;
		 float oldFitness=condTarget.getQuality();
		ArrayList<Chromosome> l = new ArrayList<Chromosome>(Arrays.asList(tracker.branchCoveredByIndividual));
		 l.removeAll(Collections.singleton(null)); 		 
//		 System.out.println("BR COVERAGE ( " + l.size()  +" ): ");
		 
		 Map<Class<?>, List<String>> recommendMethods=calculateMethodsRecommend(BR);
			Map<Class<?>, List<String>> origRecommendMethods = new LinkedHashMap<Class<?>, List<String>>();
			origRecommendMethods.putAll(recommendMethods);
			
		for (int i = 0; i < ITERATIONS && elapsed < TestTimeout; i++) {

//		System.out.println("HillClimbing.hillClimb  Origi. individual:\t"+ originalIndividual.toString());
//			System.out.println("b4 individual:"+ individual.toString());
//			System.out.println("--------------------------");
			if(--ttl < 0) {
				recommendMethods.putAll(origRecommendMethods);
//				List<Class<?>> keys = new ArrayList<Class<?>>(recommendMethods.keySet());
//				int cutRand = ValuesGenerator.getRandomInt(recommendMethods.size());
//				Class<?> cName = (Class<?>) keys.get(cutRand);
//				List<String> recMethodList=	recommendMethods.get(cName);
//				int methodRand = ValuesGenerator.getRandomInt(recMethodList.size());
//				List<String> temp=new LinkedList<String>();
//				temp.add(recMethodList.get(methodRand));
//				recommendMethods.clear();
//				recommendMethods.put(cName, temp);
				
				ttl = TTL_FIRST;
			}

			boolean canContinue = mutate(individual,recommendMethods,BR);
			
//			if(BR==139 ||BR==138){
//				System.out.println("Original individual:"+ originalIndividual.toString());
//				System.out.println("af individual:"+ individual.toString()+"\n");
//			}
			
			ReflectionExecutor.execute(individual);
			
			tracker.calculateFitness(individual); 
			tracker.reset();
			ArrayList<Chromosome>  l2 = new ArrayList<Chromosome>(Arrays.asList(tracker.branchCoveredByIndividual));
			l2.removeAll(Collections.singleton(null)); 		 
			 condTarget = (CoverageBranchTarget) trackers.get(BR);
			 if(condTarget==null) return null;
			if(l2.size() >l.size() ){
				 if (!GAConfig.Silent) 
					System.out.println("** New BR COVERAGE ( " + l2.size()  +" ): ");
				 l = new ArrayList<Chromosome>(Arrays.asList(tracker.branchCoveredByIndividual));
				 l.removeAll(Collections.singleton(null)); 	
				 newIndividual=individual.clone();
				 if(condTarget.getQuality()==Float.NEGATIVE_INFINITY)
					 	return newIndividual;
			}


				 float newFitness=condTarget.getQuality();
				 if(newFitness>=oldFitness){
						 individual=originalIndividual.clone();
				 }
				 else{
					 originalIndividual=individual.clone();
					 oldFitness=newFitness;
					 ttl = TTL_IMPROVEMENT;
				 }
				 elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
				
		}
		return newIndividual;
		
	}
	public Map<Class<?>, List<String>> calculateMethodsRecommend(int BR) {
//		if(BR==132||BR==91)
//			System.out.println("debug");
		List<String> methodsList = new LinkedList<String>();
		Map<Class<?>, List<String>> methodsRecommend = new LinkedHashMap<Class<?>, List<String>>();
		Map<String, List<String>> writeMethods=new LinkedHashMap<String, List<String>>();
		String mName = null;
		Class<?> cName = findFieldTarget.getClassName(BR,tracker);
		String tField = findFieldTarget.detectField(BR,tracker);

		if (tField == null||tField.equals("parameter") ) {
			MethodRelations relations = gaInfo.getMethodRelations();
			mName = findFieldTarget.getMethod(BR,tracker);
			if (mName != null)
				methodsList.add(mName);
			else
				mName = findFieldTarget.getPrivateMethod(BR,tracker);

				//get write methods for the mName that includes the branc BR 
				// writeMethods = relations.getCallPrivateMethods(cName,mName);
                 Map<Class<?>, Map<String, List<String>>> writePrivateMethods = relations.buildMethodsCallPrivate(mName);
                 writeMethods = writePrivateMethods.get(cName);

			if(writeMethods!=null )
				methodsList.addAll(writeMethods.get(mName));
			methodsRecommend.put(cName, methodsList);
//			System.out.print("");

		} else {
			if (!tField.equals("")){
				MethodRelations relations = gaInfo.getMethodRelations();
				Map<Class<?>, Map<String, List<String>>> writeFieldMethods = relations
						.buildWriteFiledMethodDependence();
				Map<String, List<String>> fieldsMethods = writeFieldMethods
						.get(cName);
				if(fieldsMethods!=null)
					methodsList = fieldsMethods.get(tField);
				//there is no write methods for the specific field tField!
				if(methodsList.size()==0){
					Map<Class<?>,List<String>> allWriteMethods=relations.getAllWriteMethods();
					if(allWriteMethods.size()!=0)
							methodsList = allWriteMethods.get(cName);
				}
				
				mName = findFieldTarget.getMethod(BR,tracker);
				if (mName != null)
					methodsList.add(mName);
				else{
					mName = findFieldTarget.getPrivateMethod(BR,tracker);
                     Map<Class<?>, Map<String, List<String>>> writePrivateMethods = relations.buildMethodsCallPrivate(mName);
                     writeMethods = writePrivateMethods.get(cName);

    				if(writeMethods!=null ){
    	                List<String> writeMethodsList = writeMethods.get(mName);
    	                // intersection as set
    	                Set<String> intersect = new HashSet<String>(writeMethodsList);
    	                intersect.retainAll(methodsList);
    	                if(intersect.size()!=0){
	    	                methodsList.clear();
	    	                methodsList.addAll(intersect);
    	                }
    	                else
    	                	if(writeMethodsList.size()!=0)
    	                		methodsList.addAll(writeMethodsList);

					}
				}
			}
			if(methodsList.size()!=0)
					methodsRecommend.put(cName, methodsList);
			else
				System.err.print("HillClimbing.calculateMethodsRecommend: There are no recommended methods to the target#"+ BR);
		}
		return methodsRecommend;
	}
	/** probability to add an operation before the selected operation */
	private float probAdd = 0.2f;

	/** probability to remove the selected operation */
	private float probRemove = 0.2f;

	/** if an AssignPrimitive is selected, this is the probability to modify the value */
	private float probModify = 0.6f;

	private String objCallee=null;
	private String objRefrence=null;

	public boolean mutate(Chromosome individual,Map<Class<?>, List<String>> methodsRecommend,int BR){
		objRefrence=null;
		String pType= Filter.PrimitiveGenericConverter(calculatePredicateType(BR));// type of the predicate field/parameter.
		String pSuggestedValue= findFieldTarget.getPredicateValue(BR,tracker);// predicate value if there is one!
	
				
		if(pSuggestedValue==null)
			pSuggestedValue=ValuesGenerator.buildValue(pType);		


		List<Operation> ops = individual.getTest();
	
		final boolean isModifiable = ops.size() >= 0  ;//&& (methodsRecommend.containsKey("init"));

		final boolean isRemovable = ops.size()>3;

		int choice=1;
		/** 0 => add; 1 => modify; 2 => remove */
		if(isModifiable && isRemovable) {
			float c = ValuesGenerator.getRandomFloat() * (probAdd + probModify + probRemove);
			if(c < probAdd) choice = 0;
			else if(c < probAdd + probModify) choice = 1;
			else choice = 2;
		} else if(isModifiable) {
			float c = ValuesGenerator.getRandomFloat() * (probAdd + probModify);
			if(c < probAdd) choice = 0;
			else choice = 1;
		} else if(isRemovable) {
			float c = ValuesGenerator.getRandomFloat()* (probAdd + probRemove);
			if(c < probAdd) choice = 0;
			else choice = 2;
		} else choice = 0;
		
//		choice=2;
		if(choice==0){
//			System.out.println("add operation");
			addOperations(individual,methodsRecommend,pSuggestedValue,pType,BR);
			modify(individual,methodsRecommend,pSuggestedValue,pType,BR);
			
		}else if(choice==1){
//			System.out.println("modify operation");
			modify(individual,methodsRecommend,pSuggestedValue,pType,BR);
		}else if(choice==2){
//			System.out.println("remove operation");
			if(ops.size()<=3) return true;
			removeOperations(individual,methodsRecommend,pSuggestedValue,pType,BR);
			modify(individual,methodsRecommend,pSuggestedValue,pType,BR);

			//			int randPos=ValuesGenerator.getRandomInt(ops.size());
//			ops.remove(randPos);
//			individual.setTest(ops);
		}else{
			System.err.println("HillCimbing.mutate: undefined operation");
		}
		return false;
	}
	private void removeOperations(Chromosome individual,Map<Class<?>, List<String>> recommendedMethods,String pSuggestedValue,String pType, int BR) {
		List<Class<?>> keys = new ArrayList<Class<?>>(recommendedMethods.keySet());
		int cutRand = ValuesGenerator.getRandomInt(recommendedMethods.size());
		Class<?> cName = (Class<?>) keys.get(cutRand);
		List<String> methodsRecommendedList=recommendedMethods.get(cName);

		List<Operation> ops = individual.getTest();
		List<Operation> newOps = new LinkedList<Operation>();
//		int randPos=ValuesGenerator.getRandomIntBetween(0,ops.size());
//		int num = ValuesGenerator.getRandomIntBetween(1,10);
		boolean methodExist=false;
		for(Operation op : ops) {
			if(op instanceof Invoke){
				Invoke m=(Invoke)op;
				 MethodDeclared method = m.getMethod();
				 if(methodsRecommendedList.contains(method.getName())){
					 methodExist=true;
					 if(ValuesGenerator.getBoleean(.5f))
						 	newOps.add(op);
				 }
			}
		}
		if(!methodExist)
			ops.clear();
		
		if(newOps.size()!=0){
			ops.clear();
			ops.addAll(newOps);
		}
	}
	
	private void addOperations(Chromosome individual,Map<Class<?>, List<String>> methodsRecommend,String pSuggestedValue,String pType, int BR) {
		List<Operation> ops = individual.getTest();
		
		int randPos=ValuesGenerator.getRandomIntBetween(0,ops.size());
		int num = ValuesGenerator.getRandomIntBetween(1,10);
		for (int i = 0; i < num; i++) {
			ops = individual.getTest();
			List<Operation> rep = new LinkedList<Operation>();
			rep.addAll(ops.subList(0, randPos));
			if (methodsRecommend == null)
				rep.add(Operation.randomlyGenerate(gaInfo.getTestCluster()));
			else{
				Operation newOp=Operation.recommendMethodsGenerateOperation(gaInfo.getTestCluster(), methodsRecommend,pSuggestedValue,pType);
				if(newOp!=null)
					rep.add(newOp);
				else
					continue;
			}

			rep.addAll(ops.subList(randPos, ops.size()));
			individual.setTest(rep);
		}	
	}

	private void modify(Chromosome individual,Map<Class<?>, List<String>> recommendedMethods,String pSuggestValueInit,String pType, int BR) {
		//choose a method
		List<Class<?>> keys = new ArrayList<Class<?>>(recommendedMethods.keySet());
		int cutRand = ValuesGenerator.getRandomInt(recommendedMethods.size());
		Class<?> cName = (Class<?>) keys.get(cutRand);
		List<String> methodsRecommendedList=recommendedMethods.get(cName);
		int methodRand = ValuesGenerator.getRandomInt(methodsRecommendedList.size());
		String targetPublicMethod=(String) methodsRecommendedList.get(methodRand);

			String pSuggestValue=pSuggestValueInit;
		//the function that change the value is the construction of a class
		if(methodsRecommendedList.size()==1 && methodsRecommendedList.contains("<init>")){
			//String cName,Map<String, String[]>  refFactory
			objRefrence=Operation.getReference (cName.getName(),individual.getReferenceFactory());
			modifyConstructoParam(individual,cName.getName(),pSuggestValueInit, pType,objRefrence, BR);
			return;
		}
//		if(targetPublicMethod.contains("<init>")){
//			modifyConstructoParam(individual,cName.getName(),pSuggestValue, pType, BR);
//			return;
//		}
		boolean methodIsStatic=findFieldTarget.isCallee1This(BR,tracker);
		List<Operation> ops =individual.getTest();
		for(int i=ops.size()-1 ;i>=0 && objRefrence==null ;i--)
			if(ops.get(i) instanceof Invoke){
				Invoke op=(Invoke) ops.get(i);

				final MethodDeclared method =  op.getMethod();
				
				//if(!method.getName().equals(targetPublicMethod)) continue;
				if(!methodsRecommendedList.contains(method.getName()))continue;
				methodIsStatic=method.isStatic();
				objCallee= findFieldTarget.getCallee(BR, tracker);
				if(objCallee!=null)
				if(objCallee.contains("@this") && !methodIsStatic){
					objRefrence=op.getThis();
				}

				final Class<?>[] paramsTypes = method.getParameterTypes();
				String[] p = new String[paramsTypes.length];
				String[] params = new String[paramsTypes.length];
				String[] actualParams=op.getParams();
				for(int k= 0; k < p.length &&objRefrence==null; k++) {
					params[k]=actualParams[k];
//					params[k]=actualParams[k].toString();
					Class<?> paramClazz=paramsTypes[k];
					if(Filter.isPrimitiveOrStringType(paramClazz)){
						String paramType=Filter.PrimitiveGenericConverter(paramClazz.getName());
						if(paramType.equals("java.lang.String"))
							if(paramType.equals(pType)||(paramType.equals("java.lang.String")&&pType.equals("java.lang.Character")))
								pSuggestValue=ValuesGenerator.stringValueMuteated(params[k],pSuggestValueInit);
						if(paramType.equals(pType)||(paramType.equals("java.lang.String")&&pType.equals("java.lang.Character"))){
//							if((BR % 2)==0)
//								pSuggestValue=ValuesGenerator.buildValue(paramType);		

								params[k]=pSuggestValue;			
						}
						}
					else{
						if(objCallee!=null)
							if(objCallee.contains("java.lang.Object")&& !methodIsStatic){
								if(!(cName.getName().equals(method.getReplacementObjectParam().getName())))
									params[k] = Operation.generateRef(method,cName, individual.getReferenceFactory(),individual.getCluster());
								objRefrence=params[k];
							}
						}
					op.setParams(params);
					}
				}
		if(!methodIsStatic)
			if(objRefrence!=null){
				if(methodsRecommendedList.contains("<init>")){
					modifyConstructoParam(individual,cName.getName(),pSuggestValue, pType,objRefrence, BR);
				return;
				}
			}else
				probModify = 0.0f; //there is no calling for the public method!
	
		
	}
	
	private void modifyConstructoParam(Chromosome individual,String cName,String pSuggestValue,String pType,String objRefrence, int BR){
		TestClusterBuilder TestCluster=gaInfo.getTestCluster();
		Map<String, String[]> refFactory = TestCluster.getRefFactory();
		Map<String, List<CreateObject>> constructorz=individual.getConstructors();
		Map<String, Class<?>> cluster = individual.getCluster();

		String targetObj=null;
	//	if(findFieldTarget.canBothFieldsBeUsed(BR, tracker))
		List<CreateObject> listObject=constructorz.get(cName);
		//constructorz.clear();
		if (listObject != null) {
			for(CreateObject obj : listObject){

				ConstructorDeclared cut= (ConstructorDeclared) obj.getConstructor();//.clone();
				if(!findFieldTarget.canBothFieldsBeUsed(BR, tracker))//if the predicate contains both fields of the class if (obj1.price==obj2.price)
					if(!objRefrence.equals(cut.getRefrence())) continue;
				Class<?>[] paramsType = cut.getParameterTypes();
				Object[] actualParams = new Object[cut.getParameterTypes().length];
				String[] params = new String[paramsType.length];
				for (int k = 0; k < actualParams.length; k++) {
					Class<?> paramClazz=paramsType[k];
					params[k]=cut.getParams()[k];
				if (Filter.isPrimitiveOrStringType(cluster.get(paramsType[k].getName().toString()))){
					String paramType=Filter.PrimitiveGenericConverter(paramClazz.getName());
					if(paramType.equals(pType)){
							params[k]=pSuggestValue;
					}
				}
				else
					params[k] = TestCluster.getReference(paramsType[k].getName(), refFactory);
			}
				cut.setParams(params);
			}
		}
		// update all constructors that has objReference!
		List<Operation> ops =individual.getTest();
		for(int i=ops.size()-1 ;i>=0  ;i--)
			if(ops.get(i) instanceof CreateObject){
				CreateObject obj=(CreateObject) ops.get(i);
				ConstructorDeclared cut = obj.getConstructor();
				if(!findFieldTarget.canBothFieldsBeUsed(BR, tracker))//if the predicate contains both fields of the class if (obj1.price==obj2.price)
					if(!objRefrence.equals(cut.getRefrence())) continue;
				Class<?>[] paramsType = cut.getParameterTypes();
				Object[] actualParams = new Object[cut.getParameterTypes().length];
				String[] params = new String[paramsType.length];
				for (int k = 0; k < actualParams.length; k++) {
					Class<?> paramClazz=paramsType[k];
					params[k]=cut.getParams()[k];
				if (Filter.isPrimitiveOrStringType(cluster.get(paramsType[k].getName().toString()))){
					String paramType=Filter.PrimitiveGenericConverter(paramClazz.getName());
					if(paramType.equals(pType)){
							params[k]=pSuggestValue;
					}
				}
				else
					params[k] = TestCluster.getReference(paramsType[k].getName(), refFactory);
			}
				cut.setParams(params);

			}
	
		
	}

	private String calculatePredicateType(int BR){
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(BR);
		String predicateTpe=condTarget.getType();
	//	if(condTarget.getmClase()!="")
				return predicateTpe;
		
	//			if(!condTarget.getmClase().equals("")&&!condTarget.getmCall().equals(""))
	//	if(!condTarget.getmCall().startsWith("java.lang."))
	//	return null;
	}

}
